/*
 * Copyright 2011-2012 by The Regents of the University of California Licensed
 * under the Apache License, Version 2.0 (the "License"); you may not use this
 * file except in compliance with the License. you may obtain a copy of the
 * License from
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package edu.ucsd.db.bassarisk.query.expression.visitor;

import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;

import edu.ucsd.db.bassarisk.exception.QueryValidationException;
import edu.ucsd.db.bassarisk.exception.VisitorException;
import edu.ucsd.db.bassarisk.query.ValidationTraversalLog;
import edu.ucsd.db.bassarisk.query.binding.Bindings.Binding;
import edu.ucsd.db.bassarisk.query.bindingtree.BindingTreeElements.BindingTreeEdge;
import edu.ucsd.db.bassarisk.query.bindingtree.BindingTreeElements.BindingTreeNode;
import edu.ucsd.db.bassarisk.query.bindingtree.BindingTreeElements.ChoiceNode;
import edu.ucsd.db.bassarisk.query.bindingtree.BindingTreeElements.ChoiceNode.Choice;
import edu.ucsd.db.bassarisk.query.bindingtree.BindingTreeElements.ChoiceNode.ChooseAll;
import edu.ucsd.db.bassarisk.query.bindingtree.BindingTreeElements.ChoiceNode.ChooseOne;
import edu.ucsd.db.bassarisk.query.bindingtree.BindingTreeElements.EstablishingNode;
import edu.ucsd.db.bassarisk.query.expression.Expressions.BooleanAnd;
import edu.ucsd.db.bassarisk.query.expression.Expressions.BooleanExpr;
import edu.ucsd.db.bassarisk.query.expression.Expressions.BooleanNot;
import edu.ucsd.db.bassarisk.query.expression.Expressions.BooleanOr;
import edu.ucsd.db.bassarisk.query.expression.Expressions.Expression;
import edu.ucsd.db.bassarisk.query.expression.Expressions.KeywordMatchExpr;
import edu.ucsd.db.bassarisk.query.expression.Expressions.NonterminalExpr;
import edu.ucsd.db.bassarisk.query.expression.Expressions.PositionPredExpr;
import edu.ucsd.db.bassarisk.query.expression.Expressions.StructureFind;
import edu.ucsd.db.bassarisk.query.expression.Expressions.StructureIn;
import edu.ucsd.db.bassarisk.query.expression.Expressions.StructurePredExpr;
import edu.ucsd.db.bassarisk.query.expression.Expressions.StructureSame;
import edu.ucsd.db.bassarisk.treepattern.EdgeFunction;

public class BindingTreeBuildTraversal extends
    SupertypeDeferingExpressionTreeVisitor<List<BindingTreeEdge>, Void> {
  private BindingTreeEdge answerLead;
  private final ValidationTraversalLog vtl;

  public BindingTreeBuildTraversal(final ValidationTraversalLog vtl) {
    this.vtl = vtl;
  }

  public BindingTreeEdge getAnswerLead() {
    return this.answerLead;
  }

  @Override
  public List<BindingTreeEdge>
      visitBooleanAnd(final BooleanAnd expr, final Void arg) throws VisitorException {
    List<BindingTreeEdge> children = new ArrayList<BindingTreeEdge>();
    for (Expression subExpr : expr.getSubExprs()) {
      children.addAll(subExpr.accept(this, arg));
    }
    return children;
  }

  @Override
  public List<BindingTreeEdge> visitBooleanExpr(final BooleanExpr expr,
                                                final Void arg)
      throws VisitorException {
    throw VisitorException.requireSubclassImplementation();
  }

  @Override
  public List<BindingTreeEdge>
      visitBooleanNot(final BooleanNot expr, final Void arg) throws VisitorException {
    List<BindingTreeEdge> edges = expr.getSubExpr().accept(this, arg);
    for (BindingTreeEdge edge : edges) {
      edge.functions().remove(EdgeFunction.NonEmpty);
      edge.functions().add(EdgeFunction.Empty);
    }
    return edges;
  }

  @Override
  public List<BindingTreeEdge> visitBooleanOr(final BooleanOr expr, final Void arg)
      throws VisitorException {
    List<Choice> choices = new ArrayList<Choice>();
    int i = 0;
    for (Expression subExpr : expr.getSubExprs()) {
      List<BindingTreeEdge> subChoices = subExpr.accept(this, arg);
      Choice newChoice = null;
      if (subChoices.size() > 1) {
        newChoice = new ChooseAll(subChoices);
      } else if (subChoices.size() == 1) {
        newChoice = new ChooseOne(subChoices.get(0));
      } else {
        throw new RuntimeException("subChoices should never be empty");
      }
      for (Entry<String, Binding[]> entry : expr.getEstablishedBindingProducts()) {
        Binding guardBinding = entry.getValue()[i];
        if (guardBinding != null) {
          newChoice.getGuard().set(guardBinding.getId());
        }
      }
      choices.add(newChoice);
      i++;
    }
    ChoiceNode node = new ChoiceNode(choices);
    BindingTreeEdge choiceEdge = new BindingTreeEdge(node);
    choiceEdge.functions().add(EdgeFunction.NonEmpty);
    return BindingTreeEdge.list(choiceEdge);
  }

  @Override
  public List<BindingTreeEdge> visitKeywordMatchExpr(final KeywordMatchExpr expr,
                                                     final Void arg)
      throws VisitorException {
    BindingTreeNode estabNode = new EstablishingNode(expr.getEstablishedBinding());
    BindingTreeEdge estabEdge = new BindingTreeEdge(estabNode);
    estabEdge.functions().add(EdgeFunction.NonEmpty);
    return BindingTreeEdge.list(estabEdge);
  }

  @Override
  public List<BindingTreeEdge> visitNonterminalExpr(final NonterminalExpr expr,
                                                    final Void arg)
      throws VisitorException {
    throw VisitorException.requireSubclassImplementation();
  }

  @Override
  public List<BindingTreeEdge> visitPositionPredExpr(final PositionPredExpr expr,
                                                     final Void arg)
      throws VisitorException {
    throw VisitorException.expressionEliminated();
  }

  @Override
  public List<BindingTreeEdge> visitStructureFind(final StructureFind expr,
                                                  final Void arg)
      throws VisitorException {
    if (this.answerLead != null) {
      // query must have more than one "find" node. error.
      String message = "Query must have exactly one 'Find' expression to be valid";
      this.vtl.addError(new QueryValidationException(message));
    }
    EstablishingNode answerNode =
        new EstablishingNode(expr.getEstablishedBinding(), expr.getSubExpr()
            .accept(this, arg));
    this.answerLead = new BindingTreeEdge(answerNode);
    this.answerLead.functions().add(EdgeFunction.Values);
    return BindingTreeEdge.list(this.answerLead);
  }

  @Override
  public List<BindingTreeEdge> visitStructureIn(final StructureIn expr,
                                                final Void arg)
      throws VisitorException {
    throw VisitorException.expressionEliminated();
  }

  @Override
  public List<BindingTreeEdge> visitStructurePredExpr(final StructurePredExpr expr,
                                                      final Void arg)
      throws VisitorException {
    throw VisitorException.requireSubclassImplementation();
  }

  @Override
  public List<BindingTreeEdge> visitStructureSame(final StructureSame expr,
                                                  final Void arg)
      throws VisitorException {
    BindingTreeNode estabNode =
        new EstablishingNode(expr.getEstablishedBinding(), expr.getSubExpr()
            .accept(this, arg));
    BindingTreeEdge estabEdge = new BindingTreeEdge(estabNode);
    estabEdge.functions().add(EdgeFunction.NonEmpty);
    return BindingTreeEdge.list(estabEdge);
  }
}
